<template>
  <div class="hello">
    <el-container>
      <el-header>
        <el-button @click="addDialogVisible = true" type="success" round>新增</el-button>
        <el-button @click="dialogVisible = true" type="success" round>连接</el-button>
        <el-button @click="handleTabsEdit('', 'add')" type="success" round>addTab</el-button>
      </el-header>
      <el-container>
        <el-aside width="100px">
          el-aside
        </el-aside>
        <el-main>
          <el-tabs v-model="editableTabsValue" type="card" editable @edit="handleTabsEdit">
            <el-tab-pane :key="item.name" v-for="(item, index) in editableTabs" :label="item.title" :name="item.name">
              <div class="ssh-container" ref="terminal" :id="item.name"></div>
            </el-tab-pane>
          </el-tabs>
        </el-main>
      </el-container>
      <el-footer>Footer</el-footer>
    </el-container>
    <el-dialog title="新增" :visible.sync="addDialogVisible" width="60%" :before-close="handleClose">
      <span>
        <el-input placeholder="请输入名称" v-model="name">
          <template slot="prepend">名称</template>
        </el-input>
        <el-input placeholder="请输入主机" v-model="host">
          <template slot="prepend">主机</template>
        </el-input>
        <el-input placeholder="请输入端口" v-model="port">
          <template slot="prepend">端口</template>
        </el-input>
        <el-input placeholder="请输入用户名" v-model="uname">
          <template slot="prepend">用户名</template>
        </el-input>
        <el-input placeholder="请输入密码" v-model="passwd" show-password>
          <template slot="prepend">密码</template>
        </el-input>
      </span>
      <el-button type="primary" @click="saveAndConn(true)">保存并连接</el-button>
      <el-button type="primary" @click="saveAndConn(false)">保存</el-button>
    </el-dialog>
    <el-dialog title="连接管理" :visible.sync="dialogVisible" width="60%" :before-close="handleClose">
      <span>
        <el-tree :data="data" :props="defaultProps" accordion @node-click="handleNodeClick">
        </el-tree>
      </span>
      <el-button @click="dialogVisible = false">取 消</el-button>
      <el-button type="primary" @click="dialogVisible = false">确 定</el-button>
    </el-dialog>
  </div>
</template>

<script>
  import {
    Terminal
  } from 'xterm'
  import {
    FitAddon
  } from 'xterm-addon-fit'
  import 'xterm/css/xterm.css'
  import {
    debounce
  } from 'lodash'

  const packSave = (isConn, termName, name, host, port, uname, passwd) =>
    JSON.stringify({
      isConn: isConn,
      termName: termName,
      name: name,
      host: host,
      port: port,
      uname: uname,
      passwd: passwd
    })

  const packStdin = data =>
    JSON.stringify({
      Op: 'stdin',
      Data: data
    })

  const packResize = (cols, rows) =>
    JSON.stringify({
      Op: 'resize',
      Cols: cols,
      Rows: rows
    })

  var term = null;
  var localTerm = [];
  var first = true;
  // 当前行命令
  var cLineData = '';

  export default {
    name: 'zshell',
    data() {
      return {
        name: '',
        host: '',
        port: '',
        uname: '',
        passwd: '',
        addDialogVisible: false,
        dialogVisible: false,
        msg: 'Welcome to Your Vue.js App',
        initText: '正在初始化...\r\n',
        fitAddon: null,
        option: {
          lineHeight: 1.0,
          cursorBlink: true,
          cursorStyle: 'block', // 光标样式 'block' | 'underline' | 'bar'
          fontSize: 18,
          fontFamily: "Monaco, Menlo, Consolas, 'Courier New', monospace",
          theme: {
            background: '#181d28'
          },
          cols: 30 // 初始化的时候不要设置fit，设置col为较小值（最小为可展示initText初始文字即可）方便屏幕缩放
        },
        data: [{
          label: 'ROOT',
        }],
        defaultProps: {
          children: 'children',
          label: 'label'
        },
        editableTabsValue: 'term1',
        editableTabs: [{
          title: 'Term1',
          name: 'term1',
        }],
        tabIndex: 1
      }
    },
    mounted() {
      this.onRenderer();

      this.initTerm();
      this.initIpc();

      this.onTerminalResize();
      //this.resizeRemoteTerminal();

      this.onTerminalKeyPress();
      //this.fit();
    },
    beforeDestroy() {
      this.removeResizeListener();
      term && term.dispose();
    },
    methods: {
      saveAndConn(isConn) {
        if (isConn) {
          this.handleTabsEdit(this.editableTabsValue, 'add');
          localTerm['term1'] = false;
        }
        window.ipc.terminal(packSave(isConn, this.editableTabsValue, this.name, this.host, this.port, this.uname, this.passwd));
        this.addDialogVisible = false;
      },
      handleClose(done) {
        done();
      },
      handleTabsEdit(targetName, action) {
        if (action === 'add') {
          ++this.tabIndex;
          let newTabName = 'term' + this.tabIndex;
          this.editableTabs.push({
            title: 'Term' + this.tabIndex,
            name: newTabName
          });
          this.editableTabsValue = newTabName;
        }
        if (action === 'remove') {
          let tabs = this.editableTabs;
          let activeName = this.editableTabsValue;
          if (activeName === targetName) {
            tabs.forEach((tab, index) => {
              if (tab.name === targetName) {
                let nextTab = tabs[index + 1] || tabs[index - 1];
                if (nextTab) {
                  activeName = nextTab.name;
                }
              }
            });
          }

          this.editableTabsValue = activeName;
          this.editableTabs = tabs.filter(tab => tab.name !== targetName);
        }
      },
      handleNodeClick(data) {
        console.log(data);
      },
      addPop: function () {
        console.log('addPop');
      },
      sendMsg: function () {
        window.ipc.terminal("connect");
      },
      onRenderer: function () {
        window.ipc.onRendererToRenderer(function (event, arg1) {
          console.log('onRendererToRenderer renderer on: ', arg1);
        });
      },

      initTerm() {
        term = new Terminal(this.option)
        this.term = term;
        this.fitAddon = new FitAddon()
        term.loadAddon(this.fitAddon)
        //term.open(this.$refs.terminal)
        term.open(document.getElementById("term1"))
        // this.fitAddon.fit() // 初始化的时候不要使用fit
        setTimeout(() => {
          this.fitAddon.fit()
          //term.clear();
          //term.write("\x1b[33m$\x1b[0m ")
        }, 2000); // 必须延时处理

        window.ipc.init();
        localTerm['term1'] = true;
      },
      onTerminalKeyPress() {
        // 定义变量获取整行数据
        let currentLineData = ''
        // 历史行输入数据
        let historyLineData = []
        let last = 0
        // 监听键盘输入事件
        // 使其能够输入汉字
        term.onData(async key => {
          console.log(key);
          console.log(key.charCodeAt(0));
          //enter键
          if (key.charCodeAt(0) === 13) {
            // 将行数据进行添加进去
            if (currentLineData !== '') {
              //将当前行数据传入历史命令数组中存储
              historyLineData.push(currentLineData)
              //定义当前行命令在整个数组中的位置
              last = historyLineData.length - 1
              term.write('\r\n')
            }
            //当输入clear时清空终端内容
            if (currentLineData === 'clear') {
              term.clear()
            }

            //在这可以进行发起请求将整行数据传入
            //发送数据获取结果
            window.ipc.stdin(localTerm['term1'], packStdin(currentLineData + '\r'));
            cLineData = currentLineData;

            // 清空当前行数据
            currentLineData = ''
          } else if (key.charCodeAt(0) === 127) {
            //删除键-->不能删除提示符
            if (currentLineData.length > 0) {
              currentLineData = currentLineData.slice(0, -1)
              term.write('\b \b')
            }
          } else if (key === '\u001b[A') {
            //up键的时候
            let len = 0
            if (historyLineData.length > 0) {
              len = historyLineData.length + 1
            }

            if (last < len && last > 0) {
              //当前行有数据的时候进行删除掉在进行渲染上存储的历史数据
              for (let i = 0; i < currentLineData.length; i++) {
                if (currentLineData.length > 0) {
                  term.write('\b \b')
                }
              }
              let text = historyLineData[last - 1]
              term.write(text)
              //重点，一定要记住存储当前行命令保证下次up或down时不会光标错乱覆盖终端提示符
              currentLineData = text

              last--
            }
          } else if (key === '\u001b[B') {
            //down键
            let lent = 0
            if (historyLineData.length > 0) {
              lent = historyLineData.length - 1
            }
            if (last < lent && last > -1) {
              for (let i = 0; i < currentLineData.length; i++) {
                if (currentLineData.length > 0) {
                  term.write('\b \b')
                }
              }
              let text = historyLineData[last + 1]
              term.write(text)
              currentLineData = text
              last++
            }
          } else {
            //啥也不做的时候就直接输入
            currentLineData += key
            term.write(key)
          }
        })
      },
      // resize 相关
      resizeRemoteTerminal() {
        const {
          cols,
          rows
        } = term
        console.log('列数、行数设置为：', cols, rows)
        // 调整后端终端大小 使后端与前端终端大小一致
        window.ipc.stdin(localTerm['term1'], packResize(cols, rows));
        //this.isWsOpen() && this.ws.send(packResize(cols, rows))
      },
      onResize: debounce(function () {
        //console.log('this.fitAddon.fit()');
        this.fitAddon.fit();
      }, 500),
      onTerminalResize() {
        window.addEventListener('resize', this.onResize)
        term.onResize(this.resizeRemoteTerminal)
      },
      removeResizeListener() {
        window.removeEventListener('resize', this.onResize)
      },
      // ipc
      initIpc() {
        term.write(this.initText)
        this.onMessage()
      },
      // 接收消息
      onMessage() {
        window.ipc.onTerminal(function (event, termName, msg) {
          //const term = this.term
          // 第一次连接成功将 initText 清空
          if (first) {
            first = false
            term.reset()
            term.element && term.focus()
            this.resizeRemoteTerminal()
          }
          console.log('recive msg:' + msg);
          let line = cLineData + '\n';
          if (line !== msg) {
            term.write(msg)
          }
        });
      }
    }
  }

</script>

<!-- Add "scoped" attribute to limit CSS to this component only -->
<style scoped>
  h1,
  h2 {
    font-weight: normal;
  }

  ul {
    list-style-type: none;
    padding: 0;
  }

  li {
    display: inline-block;
    margin: 0 10px;
  }

  a {
    color: #42b983;
  }

  body {
    margin: 0;
    padding: 0;
  }

  .ssh-container {
    overflow: hidden;
    height: 100vh;
    border-radius: 4px;
    background: rgb(24, 29, 40);
    padding: 0px;
    color: rgb(255, 255, 255);

    .xterm-scroll-area::-webkit-scrollbar-thumb {
      background-color: #b7c4d1;
      /* 滚动条的背景颜色 */
    }

  }

  .el-header,
  .el-footer {
    background-color: #B3C0D1;
    color: #333;
    text-align: center;
    line-height: 60px;
  }

  .el-aside {
    background-color: #D3DCE6;
    color: #333;
    text-align: center;
    line-height: 200px;
  }

  .el-main {
    background-color: #E9EEF3;
    color: #333;
    text-align: center;
  }

</style>
